﻿//--------------------------------------------------------------------------------------
// File: DDSTextureLoader.cpp
//
// Functions for loading a DDS texture and creating a Direct3D 11 runtime
// resource for it
//
// Note these functions are useful as a light-weight runtime loader for DDS
// files. For a full-featured DDS file reader, writer, and texture processing
// pipeline see the 'Texconv' sample and the 'DirectXTex' library.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------

#include <algorithm>
#include <assert.h>
#include <memory>
#include <wrl.h>

#include "DDSTextureLoader.h"

using namespace Microsoft::WRL;

#if !defined(NO_D3D11_DEBUG_NAME) && (defined(_DEBUG) || defined(PROFILE))
#pragma comment(lib, "dxguid.lib")
#endif

using namespace DirectX;

//--------------------------------------------------------------------------------------
// Macros
//--------------------------------------------------------------------------------------
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3)                                         \
  ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) |                \
      ((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24))
#endif /* defined(MAKEFOURCC) */

//--------------------------------------------------------------------------------------
// DDS file structure definitions
//
// See DDS.h in the 'Texconv' sample and the 'DirectXTex' library
//--------------------------------------------------------------------------------------
#pragma pack(push, 1)

const uint32_t DDS_MAGIC = 0x20534444; // "DDS "

struct DDS_PIXELFORMAT {
  uint32_t size;
  uint32_t flags;
  uint32_t fourCC;
  uint32_t RGBBitCount;
  uint32_t RBitMask;
  uint32_t GBitMask;
  uint32_t BBitMask;
  uint32_t ABitMask;
};

#define DDS_FOURCC 0x00000004    // DDPF_FOURCC
#define DDS_RGB 0x00000040       // DDPF_RGB
#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
#define DDS_ALPHA 0x00000002     // DDPF_ALPHA

#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH

#define DDS_HEIGHT 0x00000002 // DDSD_HEIGHT
#define DDS_WIDTH 0x00000004  // DDSD_WIDTH

#define DDS_CUBEMAP_POSITIVEX                                                  \
  0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
#define DDS_CUBEMAP_NEGATIVEX                                                  \
  0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
#define DDS_CUBEMAP_POSITIVEY                                                  \
  0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
#define DDS_CUBEMAP_NEGATIVEY                                                  \
  0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
#define DDS_CUBEMAP_POSITIVEZ                                                  \
  0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
#define DDS_CUBEMAP_NEGATIVEZ                                                  \
  0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ

#define DDS_CUBEMAP_ALLFACES                                                   \
  (DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX | DDS_CUBEMAP_POSITIVEY |     \
      DDS_CUBEMAP_NEGATIVEY | DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ)

#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP

enum DDS_MISC_FLAGS2 {
  DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L,
};

struct DDS_HEADER {
  uint32_t size;
  uint32_t flags;
  uint32_t height;
  uint32_t width;
  uint32_t pitchOrLinearSize;
  uint32_t depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags
  uint32_t mipMapCount;
  uint32_t reserved1[11];
  DDS_PIXELFORMAT ddspf;
  uint32_t caps;
  uint32_t caps2;
  uint32_t caps3;
  uint32_t caps4;
  uint32_t reserved2;
};

struct DDS_HEADER_DXT10 {
  DXGI_FORMAT dxgiFormat;
  uint32_t resourceDimension;
  uint32_t miscFlag; // see D3D11_RESOURCE_MISC_FLAG
  uint32_t arraySize;
  uint32_t miscFlags2;
};

#pragma pack(pop)

//--------------------------------------------------------------------------------------
namespace {

struct handle_closer {
  void operator()(HANDLE h) {
    if (h)
      CloseHandle(h);
  }
};

typedef public std::unique_ptr<void, handle_closer> ScopedHandle;

inline HANDLE safe_handle(HANDLE h) {
  return (h == INVALID_HANDLE_VALUE) ? 0 : h;
}

template <UINT TNameLength>
inline void SetDebugObjectName(
    _In_ ID3D11DeviceChild* resource, _In_ const char (&name)[TNameLength]) {
#if defined(_DEBUG) || defined(PROFILE)
  resource->SetPrivateData(WKPDID_D3DDebugObjectName, TNameLength - 1, name);
#else
  UNREFERENCED_PARAMETER(resource);
  UNREFERENCED_PARAMETER(name);
#endif
}

}; // namespace

//--------------------------------------------------------------------------------------
static HRESULT LoadTextureDataFromFile(_In_z_ const wchar_t* fileName,
    std::unique_ptr<uint8_t[]>& ddsData, DDS_HEADER** header, uint8_t** bitData,
    size_t* bitSize) {
  if (!header || !bitData || !bitSize) {
    return E_POINTER;
  }

  // open the file
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
  ScopedHandle hFile(safe_handle(CreateFile2(
      fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr)));
#else
  ScopedHandle hFile(
      safe_handle(CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr,
          OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)));
#endif

  if (!hFile) {
    return HRESULT_FROM_WIN32(GetLastError());
  }

  // Get the file size
  LARGE_INTEGER FileSize = {0};

#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
  FILE_STANDARD_INFO fileInfo;
  if (!GetFileInformationByHandleEx(
          hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) {
    return HRESULT_FROM_WIN32(GetLastError());
  }
  FileSize = fileInfo.EndOfFile;
#else
  GetFileSizeEx(hFile.get(), &FileSize);
#endif

  // File is too big for 32-bit allocation, so reject read
  if (FileSize.HighPart > 0) {
    return E_FAIL;
  }

  // Need at least enough data to fill the header and magic number to be a valid
  // DDS
  if (FileSize.LowPart < (sizeof(DDS_HEADER) + sizeof(uint32_t))) {
    return E_FAIL;
  }

  // create enough space for the file data
  ddsData.reset(new (std::nothrow) uint8_t[FileSize.LowPart]);
  if (!ddsData) {
    return E_OUTOFMEMORY;
  }

  // read the data in
  DWORD BytesRead = 0;
  if (!ReadFile(
          hFile.get(), ddsData.get(), FileSize.LowPart, &BytesRead, nullptr)) {
    return HRESULT_FROM_WIN32(GetLastError());
  }

  if (BytesRead < FileSize.LowPart) {
    return E_FAIL;
  }

  // DDS files always start with the same magic number ("DDS ")
  uint32_t dwMagicNumber = *(const uint32_t*)(ddsData.get());
  if (dwMagicNumber != DDS_MAGIC) {
    return E_FAIL;
  }

  auto hdr = reinterpret_cast<DDS_HEADER*>(ddsData.get() + sizeof(uint32_t));

  // Verify header to validate DDS file
  if (hdr->size != sizeof(DDS_HEADER) ||
      hdr->ddspf.size != sizeof(DDS_PIXELFORMAT)) {
    return E_FAIL;
  }

  // Check for DX10 extension
  bool bDXT10Header = false;
  if ((hdr->ddspf.flags & DDS_FOURCC) &&
      (MAKEFOURCC('D', 'X', '1', '0') == hdr->ddspf.fourCC)) {
    // Must be long enough for both headers and magic value
    if (FileSize.LowPart <
        (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_DXT10))) {
      return E_FAIL;
    }

    bDXT10Header = true;
  }

  // setup the pointers in the process request
  *header = hdr;
  ptrdiff_t offset = sizeof(uint32_t) + sizeof(DDS_HEADER) +
                     (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);
  *bitData = ddsData.get() + offset;
  *bitSize = FileSize.LowPart - offset;

  return S_OK;
}

//--------------------------------------------------------------------------------------
// Return the BPP for a particular format
//--------------------------------------------------------------------------------------
static size_t BitsPerPixel(_In_ DXGI_FORMAT fmt) {
  switch (fmt) {
  case DXGI_FORMAT_R32G32B32A32_TYPELESS:
  case DXGI_FORMAT_R32G32B32A32_FLOAT:
  case DXGI_FORMAT_R32G32B32A32_UINT:
  case DXGI_FORMAT_R32G32B32A32_SINT:
    return 128;

  case DXGI_FORMAT_R32G32B32_TYPELESS:
  case DXGI_FORMAT_R32G32B32_FLOAT:
  case DXGI_FORMAT_R32G32B32_UINT:
  case DXGI_FORMAT_R32G32B32_SINT:
    return 96;

  case DXGI_FORMAT_R16G16B16A16_TYPELESS:
  case DXGI_FORMAT_R16G16B16A16_FLOAT:
  case DXGI_FORMAT_R16G16B16A16_UNORM:
  case DXGI_FORMAT_R16G16B16A16_UINT:
  case DXGI_FORMAT_R16G16B16A16_SNORM:
  case DXGI_FORMAT_R16G16B16A16_SINT:
  case DXGI_FORMAT_R32G32_TYPELESS:
  case DXGI_FORMAT_R32G32_FLOAT:
  case DXGI_FORMAT_R32G32_UINT:
  case DXGI_FORMAT_R32G32_SINT:
  case DXGI_FORMAT_R32G8X24_TYPELESS:
  case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
  case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
  case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
  case DXGI_FORMAT_Y416:
  case DXGI_FORMAT_Y210:
  case DXGI_FORMAT_Y216:
    return 64;

  case DXGI_FORMAT_R10G10B10A2_TYPELESS:
  case DXGI_FORMAT_R10G10B10A2_UNORM:
  case DXGI_FORMAT_R10G10B10A2_UINT:
  case DXGI_FORMAT_R11G11B10_FLOAT:
  case DXGI_FORMAT_R8G8B8A8_TYPELESS:
  case DXGI_FORMAT_R8G8B8A8_UNORM:
  case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
  case DXGI_FORMAT_R8G8B8A8_UINT:
  case DXGI_FORMAT_R8G8B8A8_SNORM:
  case DXGI_FORMAT_R8G8B8A8_SINT:
  case DXGI_FORMAT_R16G16_TYPELESS:
  case DXGI_FORMAT_R16G16_FLOAT:
  case DXGI_FORMAT_R16G16_UNORM:
  case DXGI_FORMAT_R16G16_UINT:
  case DXGI_FORMAT_R16G16_SNORM:
  case DXGI_FORMAT_R16G16_SINT:
  case DXGI_FORMAT_R32_TYPELESS:
  case DXGI_FORMAT_D32_FLOAT:
  case DXGI_FORMAT_R32_FLOAT:
  case DXGI_FORMAT_R32_UINT:
  case DXGI_FORMAT_R32_SINT:
  case DXGI_FORMAT_R24G8_TYPELESS:
  case DXGI_FORMAT_D24_UNORM_S8_UINT:
  case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
  case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
  case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
  case DXGI_FORMAT_R8G8_B8G8_UNORM:
  case DXGI_FORMAT_G8R8_G8B8_UNORM:
  case DXGI_FORMAT_B8G8R8A8_UNORM:
  case DXGI_FORMAT_B8G8R8X8_UNORM:
  case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
  case DXGI_FORMAT_B8G8R8A8_TYPELESS:
  case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
  case DXGI_FORMAT_B8G8R8X8_TYPELESS:
  case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
  case DXGI_FORMAT_AYUV:
  case DXGI_FORMAT_Y410:
  case DXGI_FORMAT_YUY2:
    return 32;

  case DXGI_FORMAT_P010:
  case DXGI_FORMAT_P016:
    return 24;

  case DXGI_FORMAT_R8G8_TYPELESS:
  case DXGI_FORMAT_R8G8_UNORM:
  case DXGI_FORMAT_R8G8_UINT:
  case DXGI_FORMAT_R8G8_SNORM:
  case DXGI_FORMAT_R8G8_SINT:
  case DXGI_FORMAT_R16_TYPELESS:
  case DXGI_FORMAT_R16_FLOAT:
  case DXGI_FORMAT_D16_UNORM:
  case DXGI_FORMAT_R16_UNORM:
  case DXGI_FORMAT_R16_UINT:
  case DXGI_FORMAT_R16_SNORM:
  case DXGI_FORMAT_R16_SINT:
  case DXGI_FORMAT_B5G6R5_UNORM:
  case DXGI_FORMAT_B5G5R5A1_UNORM:
  case DXGI_FORMAT_A8P8:
  case DXGI_FORMAT_B4G4R4A4_UNORM:
    return 16;

  case DXGI_FORMAT_NV12:
  case DXGI_FORMAT_420_OPAQUE:
  case DXGI_FORMAT_NV11:
    return 12;

  case DXGI_FORMAT_R8_TYPELESS:
  case DXGI_FORMAT_R8_UNORM:
  case DXGI_FORMAT_R8_UINT:
  case DXGI_FORMAT_R8_SNORM:
  case DXGI_FORMAT_R8_SINT:
  case DXGI_FORMAT_A8_UNORM:
  case DXGI_FORMAT_AI44:
  case DXGI_FORMAT_IA44:
  case DXGI_FORMAT_P8:
    return 8;

  case DXGI_FORMAT_R1_UNORM:
    return 1;

  case DXGI_FORMAT_BC1_TYPELESS:
  case DXGI_FORMAT_BC1_UNORM:
  case DXGI_FORMAT_BC1_UNORM_SRGB:
  case DXGI_FORMAT_BC4_TYPELESS:
  case DXGI_FORMAT_BC4_UNORM:
  case DXGI_FORMAT_BC4_SNORM:
    return 4;

  case DXGI_FORMAT_BC2_TYPELESS:
  case DXGI_FORMAT_BC2_UNORM:
  case DXGI_FORMAT_BC2_UNORM_SRGB:
  case DXGI_FORMAT_BC3_TYPELESS:
  case DXGI_FORMAT_BC3_UNORM:
  case DXGI_FORMAT_BC3_UNORM_SRGB:
  case DXGI_FORMAT_BC5_TYPELESS:
  case DXGI_FORMAT_BC5_UNORM:
  case DXGI_FORMAT_BC5_SNORM:
  case DXGI_FORMAT_BC6H_TYPELESS:
  case DXGI_FORMAT_BC6H_UF16:
  case DXGI_FORMAT_BC6H_SF16:
  case DXGI_FORMAT_BC7_TYPELESS:
  case DXGI_FORMAT_BC7_UNORM:
  case DXGI_FORMAT_BC7_UNORM_SRGB:
    return 8;

  default:
    return 0;
  }
}

//--------------------------------------------------------------------------------------
// Get surface information for a particular format
//--------------------------------------------------------------------------------------
static void GetSurfaceInfo(_In_ size_t width, _In_ size_t height,
    _In_ DXGI_FORMAT fmt, _Out_opt_ size_t* outNumBytes,
    _Out_opt_ size_t* outRowBytes, _Out_opt_ size_t* outNumRows) {
  size_t numBytes = 0;
  size_t rowBytes = 0;
  size_t numRows = 0;

  bool bc = false;
  bool packed = false;
  bool planar = false;
  size_t bpe = 0;
  switch (fmt) {
  case DXGI_FORMAT_BC1_TYPELESS:
  case DXGI_FORMAT_BC1_UNORM:
  case DXGI_FORMAT_BC1_UNORM_SRGB:
  case DXGI_FORMAT_BC4_TYPELESS:
  case DXGI_FORMAT_BC4_UNORM:
  case DXGI_FORMAT_BC4_SNORM:
    bc = true;
    bpe = 8;
    break;

  case DXGI_FORMAT_BC2_TYPELESS:
  case DXGI_FORMAT_BC2_UNORM:
  case DXGI_FORMAT_BC2_UNORM_SRGB:
  case DXGI_FORMAT_BC3_TYPELESS:
  case DXGI_FORMAT_BC3_UNORM:
  case DXGI_FORMAT_BC3_UNORM_SRGB:
  case DXGI_FORMAT_BC5_TYPELESS:
  case DXGI_FORMAT_BC5_UNORM:
  case DXGI_FORMAT_BC5_SNORM:
  case DXGI_FORMAT_BC6H_TYPELESS:
  case DXGI_FORMAT_BC6H_UF16:
  case DXGI_FORMAT_BC6H_SF16:
  case DXGI_FORMAT_BC7_TYPELESS:
  case DXGI_FORMAT_BC7_UNORM:
  case DXGI_FORMAT_BC7_UNORM_SRGB:
    bc = true;
    bpe = 16;
    break;

  case DXGI_FORMAT_R8G8_B8G8_UNORM:
  case DXGI_FORMAT_G8R8_G8B8_UNORM:
  case DXGI_FORMAT_YUY2:
    packed = true;
    bpe = 4;
    break;

  case DXGI_FORMAT_Y210:
  case DXGI_FORMAT_Y216:
    packed = true;
    bpe = 8;
    break;

  case DXGI_FORMAT_NV12:
  case DXGI_FORMAT_420_OPAQUE:
    planar = true;
    bpe = 2;
    break;

  case DXGI_FORMAT_P010:
  case DXGI_FORMAT_P016:
    planar = true;
    bpe = 4;
    break;
  }

  if (bc) {
    size_t numBlocksWide = 0;
    if (width > 0) {
      numBlocksWide = std::max<size_t>(1, (width + 3) / 4);
    }
    size_t numBlocksHigh = 0;
    if (height > 0) {
      numBlocksHigh = std::max<size_t>(1, (height + 3) / 4);
    }
    rowBytes = numBlocksWide * bpe;
    numRows = numBlocksHigh;
    numBytes = rowBytes * numBlocksHigh;
  } else if (packed) {
    rowBytes = ((width + 1) >> 1) * bpe;
    numRows = height;
    numBytes = rowBytes * height;
  } else if (fmt == DXGI_FORMAT_NV11) {
    rowBytes = ((width + 3) >> 2) * 4;
    numRows = height * 2; // Direct3D makes this simplifying assumption,
                          // although it is larger than the 4:1:1 data
    numBytes = rowBytes * numRows;
  } else if (planar) {
    rowBytes = ((width + 1) >> 1) * bpe;
    numBytes = (rowBytes * height) + ((rowBytes * height + 1) >> 1);
    numRows = height + ((height + 1) >> 1);
  } else {
    size_t bpp = BitsPerPixel(fmt);
    rowBytes = (width * bpp + 7) / 8; // round up to nearest byte
    numRows = height;
    numBytes = rowBytes * height;
  }

  if (outNumBytes) {
    *outNumBytes = numBytes;
  }
  if (outRowBytes) {
    *outRowBytes = rowBytes;
  }
  if (outNumRows) {
    *outNumRows = numRows;
  }
}

//--------------------------------------------------------------------------------------
#define ISBITMASK(r, g, b, a)                                                  \
  (ddpf.RBitMask == r && ddpf.GBitMask == g && ddpf.BBitMask == b &&           \
      ddpf.ABitMask == a)

static DXGI_FORMAT GetDXGIFormat(const DDS_PIXELFORMAT& ddpf) {
  if (ddpf.flags & DDS_RGB) {
    // Note that sRGB formats are written using the "DX10" extended header

    switch (ddpf.RGBBitCount) {
    case 32:
      if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) {
        return DXGI_FORMAT_R8G8B8A8_UNORM;
      }

      if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000)) {
        return DXGI_FORMAT_B8G8R8A8_UNORM;
      }

      if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000)) {
        return DXGI_FORMAT_B8G8R8X8_UNORM;
      }

      // No DXGI format maps to
      // ISBITMASK(0x000000ff,0x0000ff00,0x00ff0000,0x00000000) aka
      // D3DFMT_X8B8G8R8

      // Note that many common DDS reader/writers (including D3DX) swap the
      // the RED/BLUE masks for 10:10:10:2 formats. We assume
      // below that the 'backwards' header mask is being used since it is most
      // likely written by D3DX. The more robust solution is to use the 'DX10'
      // header extension and specify the DXGI_FORMAT_R10G10B10A2_UNORM format
      // directly

      // For 'correct' writers, this should be 0x000003ff,0x000ffc00,0x3ff00000
      // for RGB data
      if (ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000)) {
        return DXGI_FORMAT_R10G10B10A2_UNORM;
      }

      // No DXGI format maps to
      // ISBITMASK(0x000003ff,0x000ffc00,0x3ff00000,0xc0000000) aka
      // D3DFMT_A2R10G10B10

      if (ISBITMASK(0x0000ffff, 0xffff0000, 0x00000000, 0x00000000)) {
        return DXGI_FORMAT_R16G16_UNORM;
      }

      if (ISBITMASK(0xffffffff, 0x00000000, 0x00000000, 0x00000000)) {
        // Only 32-bit color channel format in D3D9 was R32F
        return DXGI_FORMAT_R32_FLOAT; // D3DX writes this out as a FourCC of 114
      }
      break;

    case 24:
      // No 24bpp DXGI formats aka D3DFMT_R8G8B8
      break;

    case 16:
      if (ISBITMASK(0x7c00, 0x03e0, 0x001f, 0x8000)) {
        return DXGI_FORMAT_B5G5R5A1_UNORM;
      }
      if (ISBITMASK(0xf800, 0x07e0, 0x001f, 0x0000)) {
        return DXGI_FORMAT_B5G6R5_UNORM;
      }

      // No DXGI format maps to ISBITMASK(0x7c00,0x03e0,0x001f,0x0000) aka
      // D3DFMT_X1R5G5B5

      if (ISBITMASK(0x0f00, 0x00f0, 0x000f, 0xf000)) {
        return DXGI_FORMAT_B4G4R4A4_UNORM;
      }

      // No DXGI format maps to ISBITMASK(0x0f00,0x00f0,0x000f,0x0000) aka
      // D3DFMT_X4R4G4B4

      // No 3:3:2, 3:3:2:8, or paletted DXGI formats aka D3DFMT_A8R3G3B2,
      // D3DFMT_R3G3B2, D3DFMT_P8, D3DFMT_A8P8, etc.
      break;
    }
  } else if (ddpf.flags & DDS_LUMINANCE) {
    if (8 == ddpf.RGBBitCount) {
      if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x00000000)) {
        return DXGI_FORMAT_R8_UNORM; // D3DX10/11 writes this out as DX10
                                     // extension
      }

      // No DXGI format maps to ISBITMASK(0x0f,0x00,0x00,0xf0) aka D3DFMT_A4L4
    }

    if (16 == ddpf.RGBBitCount) {
      if (ISBITMASK(0x0000ffff, 0x00000000, 0x00000000, 0x00000000)) {
        return DXGI_FORMAT_R16_UNORM; // D3DX10/11 writes this out as DX10
                                      // extension
      }
      if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x0000ff00)) {
        return DXGI_FORMAT_R8G8_UNORM; // D3DX10/11 writes this out as DX10
                                       // extension
      }
    }
  } else if (ddpf.flags & DDS_ALPHA) {
    if (8 == ddpf.RGBBitCount) {
      return DXGI_FORMAT_A8_UNORM;
    }
  } else if (ddpf.flags & DDS_FOURCC) {
    if (MAKEFOURCC('D', 'X', 'T', '1') == ddpf.fourCC) {
      return DXGI_FORMAT_BC1_UNORM;
    }
    if (MAKEFOURCC('D', 'X', 'T', '3') == ddpf.fourCC) {
      return DXGI_FORMAT_BC2_UNORM;
    }
    if (MAKEFOURCC('D', 'X', 'T', '5') == ddpf.fourCC) {
      return DXGI_FORMAT_BC3_UNORM;
    }

    // While pre-multiplied alpha isn't directly supported by the DXGI formats,
    // they are basically the same as these BC formats so they can be mapped
    if (MAKEFOURCC('D', 'X', 'T', '2') == ddpf.fourCC) {
      return DXGI_FORMAT_BC2_UNORM;
    }
    if (MAKEFOURCC('D', 'X', 'T', '4') == ddpf.fourCC) {
      return DXGI_FORMAT_BC3_UNORM;
    }

    if (MAKEFOURCC('A', 'T', 'I', '1') == ddpf.fourCC) {
      return DXGI_FORMAT_BC4_UNORM;
    }
    if (MAKEFOURCC('B', 'C', '4', 'U') == ddpf.fourCC) {
      return DXGI_FORMAT_BC4_UNORM;
    }
    if (MAKEFOURCC('B', 'C', '4', 'S') == ddpf.fourCC) {
      return DXGI_FORMAT_BC4_SNORM;
    }

    if (MAKEFOURCC('A', 'T', 'I', '2') == ddpf.fourCC) {
      return DXGI_FORMAT_BC5_UNORM;
    }
    if (MAKEFOURCC('B', 'C', '5', 'U') == ddpf.fourCC) {
      return DXGI_FORMAT_BC5_UNORM;
    }
    if (MAKEFOURCC('B', 'C', '5', 'S') == ddpf.fourCC) {
      return DXGI_FORMAT_BC5_SNORM;
    }

    // BC6H and BC7 are written using the "DX10" extended header

    if (MAKEFOURCC('R', 'G', 'B', 'G') == ddpf.fourCC) {
      return DXGI_FORMAT_R8G8_B8G8_UNORM;
    }
    if (MAKEFOURCC('G', 'R', 'G', 'B') == ddpf.fourCC) {
      return DXGI_FORMAT_G8R8_G8B8_UNORM;
    }

    if (MAKEFOURCC('Y', 'U', 'Y', '2') == ddpf.fourCC) {
      return DXGI_FORMAT_YUY2;
    }

    // Check for D3DFORMAT enums being set here
    switch (ddpf.fourCC) {
    case 36: // D3DFMT_A16B16G16R16
      return DXGI_FORMAT_R16G16B16A16_UNORM;

    case 110: // D3DFMT_Q16W16V16U16
      return DXGI_FORMAT_R16G16B16A16_SNORM;

    case 111: // D3DFMT_R16F
      return DXGI_FORMAT_R16_FLOAT;

    case 112: // D3DFMT_G16R16F
      return DXGI_FORMAT_R16G16_FLOAT;

    case 113: // D3DFMT_A16B16G16R16F
      return DXGI_FORMAT_R16G16B16A16_FLOAT;

    case 114: // D3DFMT_R32F
      return DXGI_FORMAT_R32_FLOAT;

    case 115: // D3DFMT_G32R32F
      return DXGI_FORMAT_R32G32_FLOAT;

    case 116: // D3DFMT_A32B32G32R32F
      return DXGI_FORMAT_R32G32B32A32_FLOAT;
    }
  }

  return DXGI_FORMAT_UNKNOWN;
}

//--------------------------------------------------------------------------------------
static DXGI_FORMAT MakeSRGB(_In_ DXGI_FORMAT format) {
  switch (format) {
  case DXGI_FORMAT_R8G8B8A8_UNORM:
    return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;

  case DXGI_FORMAT_BC1_UNORM:
    return DXGI_FORMAT_BC1_UNORM_SRGB;

  case DXGI_FORMAT_BC2_UNORM:
    return DXGI_FORMAT_BC2_UNORM_SRGB;

  case DXGI_FORMAT_BC3_UNORM:
    return DXGI_FORMAT_BC3_UNORM_SRGB;

  case DXGI_FORMAT_B8G8R8A8_UNORM:
    return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;

  case DXGI_FORMAT_B8G8R8X8_UNORM:
    return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB;

  case DXGI_FORMAT_BC7_UNORM:
    return DXGI_FORMAT_BC7_UNORM_SRGB;

  default:
    return format;
  }
}

//--------------------------------------------------------------------------------------
static HRESULT FillInitData(_In_ size_t width, _In_ size_t height,
    _In_ size_t depth, _In_ size_t mipCount, _In_ size_t arraySize,
    _In_ DXGI_FORMAT format, _In_ size_t maxsize, _In_ size_t bitSize,
    _In_reads_bytes_(bitSize) const uint8_t* bitData, _Out_ size_t& twidth,
    _Out_ size_t& theight, _Out_ size_t& tdepth, _Out_ size_t& skipMip,
    _Out_writes_(mipCount* arraySize) D3D11_SUBRESOURCE_DATA* initData) {
  if (!bitData || !initData) {
    return E_POINTER;
  }

  skipMip = 0;
  twidth = 0;
  theight = 0;
  tdepth = 0;

  size_t NumBytes = 0;
  size_t RowBytes = 0;
  const uint8_t* pSrcBits = bitData;
  const uint8_t* pEndBits = bitData + bitSize;

  size_t index = 0;
  for (size_t j = 0; j < arraySize; j++) {
    size_t w = width;
    size_t h = height;
    size_t d = depth;
    for (size_t i = 0; i < mipCount; i++) {
      GetSurfaceInfo(w, h, format, &NumBytes, &RowBytes, nullptr);

      if ((mipCount <= 1) || !maxsize ||
          (w <= maxsize && h <= maxsize && d <= maxsize)) {
        if (!twidth) {
          twidth = w;
          theight = h;
          tdepth = d;
        }

        assert(index < mipCount * arraySize);
        _Analysis_assume_(index < mipCount * arraySize);
        initData[index].pSysMem = (const void*)pSrcBits;
        initData[index].SysMemPitch = static_cast<UINT>(RowBytes);
        initData[index].SysMemSlicePitch = static_cast<UINT>(NumBytes);
        ++index;
      } else if (!j) {
        // Count number of skipped mipmaps (first item only)
        ++skipMip;
      }

      if (pSrcBits + (NumBytes * d) > pEndBits) {
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
      }

      pSrcBits += NumBytes * d;

      w = w >> 1;
      h = h >> 1;
      d = d >> 1;
      if (w == 0) {
        w = 1;
      }
      if (h == 0) {
        h = 1;
      }
      if (d == 0) {
        d = 1;
      }
    }
  }

  return (index > 0) ? S_OK : E_FAIL;
}

static HRESULT FillInitData12(_In_ size_t width, _In_ size_t height,
    _In_ size_t depth, _In_ size_t mipCount, _In_ size_t arraySize,
    _In_ DXGI_FORMAT format, _In_ size_t maxsize, _In_ size_t bitSize,
    _In_reads_bytes_(bitSize) const uint8_t* bitData, _Out_ size_t& twidth,
    _Out_ size_t& theight, _Out_ size_t& tdepth, _Out_ size_t& skipMip,
    _Out_writes_(mipCount* arraySize) D3D12_SUBRESOURCE_DATA* initData) {
  if (!bitData || !initData) {
    return E_POINTER;
  }

  skipMip = 0;
  twidth = 0;
  theight = 0;
  tdepth = 0;

  size_t NumBytes = 0;
  size_t RowBytes = 0;
  const uint8_t* pSrcBits = bitData;
  const uint8_t* pEndBits = bitData + bitSize;

  size_t index = 0;
  for (size_t j = 0; j < arraySize; j++) {
    size_t w = width;
    size_t h = height;
    size_t d = depth;
    for (size_t i = 0; i < mipCount; i++) {
      GetSurfaceInfo(w, h, format, &NumBytes, &RowBytes, nullptr);

      if ((mipCount <= 1) || !maxsize ||
          (w <= maxsize && h <= maxsize && d <= maxsize)) {
        if (!twidth) {
          twidth = w;
          theight = h;
          tdepth = d;
        }

        assert(index < mipCount * arraySize);
        _Analysis_assume_(index < mipCount * arraySize);
        initData[index]./*pSysMem*/ pData = (const void*)pSrcBits;
        initData[index]./*SysMemPitch*/ RowPitch = static_cast<UINT>(RowBytes);
        initData[index]./*SysMemSlicePitch*/ SlicePitch =
            static_cast<UINT>(NumBytes);
        ++index;
      } else if (!j) {
        // Count number of skipped mipmaps (first item only)
        ++skipMip;
      }

      if (pSrcBits + (NumBytes * d) > pEndBits) {
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
      }

      pSrcBits += NumBytes * d;

      w = w >> 1;
      h = h >> 1;
      d = d >> 1;
      if (w == 0) {
        w = 1;
      }
      if (h == 0) {
        h = 1;
      }
      if (d == 0) {
        d = 1;
      }
    }
  }

  return (index > 0) ? S_OK : E_FAIL;
}

//--------------------------------------------------------------------------------------
static HRESULT CreateD3DResources(_In_ ID3D11Device* d3dDevice,
    _In_ uint32_t resDim, _In_ size_t width, _In_ size_t height,
    _In_ size_t depth, _In_ size_t mipCount, _In_ size_t arraySize,
    _In_ DXGI_FORMAT format, _In_ D3D11_USAGE usage,
    _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags,
    _In_ unsigned int miscFlags, _In_ bool forceSRGB, _In_ bool isCubeMap,
    _In_reads_opt_(mipCount* arraySize) D3D11_SUBRESOURCE_DATA* initData,
    _Outptr_opt_ ID3D11Resource** texture,
    _Outptr_opt_ ID3D11ShaderResourceView** textureView) {
  if (!d3dDevice)
    return E_POINTER;

  HRESULT hr = E_FAIL;

  if (forceSRGB) {
    format = MakeSRGB(format);
  }

  switch (resDim) {
  case D3D11_RESOURCE_DIMENSION_TEXTURE1D: {
    D3D11_TEXTURE1D_DESC desc;
    desc.Width = static_cast<UINT>(width);
    desc.MipLevels = static_cast<UINT>(mipCount);
    desc.ArraySize = static_cast<UINT>(arraySize);
    desc.Format = format;
    desc.Usage = usage;
    desc.BindFlags = bindFlags;
    desc.CPUAccessFlags = cpuAccessFlags;
    desc.MiscFlags = miscFlags & ~D3D11_RESOURCE_MISC_TEXTURECUBE;

    ID3D11Texture1D* tex = nullptr;
    hr = d3dDevice->CreateTexture1D(&desc, initData, &tex);
    if (SUCCEEDED(hr) && tex != 0) {
      if (textureView != 0) {
        D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
        memset(&SRVDesc, 0, sizeof(SRVDesc));
        SRVDesc.Format = format;

        if (arraySize > 1) {
          SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
          SRVDesc.Texture1DArray.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
          SRVDesc.Texture1DArray.ArraySize = static_cast<UINT>(arraySize);
        } else {
          SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
          SRVDesc.Texture1D.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
        }

        hr = d3dDevice->CreateShaderResourceView(tex, &SRVDesc, textureView);
        if (FAILED(hr)) {
          tex->Release();
          return hr;
        }
      }

      if (texture != 0) {
        *texture = tex;
      } else {
        SetDebugObjectName(tex, "DDSTextureLoader");
        tex->Release();
      }
    }
  } break;

  case D3D11_RESOURCE_DIMENSION_TEXTURE2D: {
    D3D11_TEXTURE2D_DESC desc;
    desc.Width = static_cast<UINT>(width);
    desc.Height = static_cast<UINT>(height);
    desc.MipLevels = static_cast<UINT>(mipCount);
    desc.ArraySize = static_cast<UINT>(arraySize);
    desc.Format = format;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.Usage = usage;
    desc.BindFlags = bindFlags;
    desc.CPUAccessFlags = cpuAccessFlags;
    if (isCubeMap) {
      desc.MiscFlags = miscFlags | D3D11_RESOURCE_MISC_TEXTURECUBE;
    } else {
      desc.MiscFlags = miscFlags & ~D3D11_RESOURCE_MISC_TEXTURECUBE;
    }

    ID3D11Texture2D* tex = nullptr;
    hr = d3dDevice->CreateTexture2D(&desc, initData, &tex);
    if (SUCCEEDED(hr) && tex != 0) {
      if (textureView != 0) {
        D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
        memset(&SRVDesc, 0, sizeof(SRVDesc));
        SRVDesc.Format = format;

        if (isCubeMap) {
          if (arraySize > 6) {
            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBEARRAY;
            SRVDesc.TextureCubeArray.MipLevels =
                (!mipCount) ? -1 : desc.MipLevels;

            // Earlier we set arraySize to (NumCubes * 6)
            SRVDesc.TextureCubeArray.NumCubes =
                static_cast<UINT>(arraySize / 6);
          } else {
            SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
            SRVDesc.TextureCube.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
          }
        } else if (arraySize > 1) {
          SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
          SRVDesc.Texture2DArray.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
          SRVDesc.Texture2DArray.ArraySize = static_cast<UINT>(arraySize);
        } else {
          SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
          SRVDesc.Texture2D.MipLevels = (!mipCount) ? -1 : desc.MipLevels;
        }

        hr = d3dDevice->CreateShaderResourceView(tex, &SRVDesc, textureView);
        if (FAILED(hr)) {
          tex->Release();
          return hr;
        }
      }

      if (texture != 0) {
        *texture = tex;
      } else {
        SetDebugObjectName(tex, "DDSTextureLoader");
        tex->Release();
      }
    }
  } break;

  case D3D11_RESOURCE_DIMENSION_TEXTURE3D: {
    D3D11_TEXTURE3D_DESC desc;
    desc.Width = static_cast<UINT>(width);
    desc.Height = static_cast<UINT>(height);
    desc.Depth = static_cast<UINT>(depth);
    desc.MipLevels = static_cast<UINT>(mipCount);
    desc.Format = format;
    desc.Usage = usage;
    desc.BindFlags = bindFlags;
    desc.CPUAccessFlags = cpuAccessFlags;
    desc.MiscFlags = miscFlags & ~D3D11_RESOURCE_MISC_TEXTURECUBE;

    ID3D11Texture3D* tex = nullptr;
    hr = d3dDevice->CreateTexture3D(&desc, initData, &tex);
    if (SUCCEEDED(hr) && tex != 0) {
      if (textureView != 0) {
        D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
        memset(&SRVDesc, 0, sizeof(SRVDesc));
        SRVDesc.Format = format;

        SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
        SRVDesc.Texture3D.MipLevels = (!mipCount) ? -1 : desc.MipLevels;

        hr = d3dDevice->CreateShaderResourceView(tex, &SRVDesc, textureView);
        if (FAILED(hr)) {
          tex->Release();
          return hr;
        }
      }

      if (texture != 0) {
        *texture = tex;
      } else {
        SetDebugObjectName(tex, "DDSTextureLoader");
        tex->Release();
      }
    }
  } break;
  }

  return hr;
}

static HRESULT CreateD3DResources12(ID3D12Device* device,
    ID3D12GraphicsCommandList* cmdList, _In_ uint32_t resDim, _In_ size_t width,
    _In_ size_t height, _In_ size_t depth, _In_ size_t mipCount,
    _In_ size_t arraySize, _In_ DXGI_FORMAT format, _In_ bool forceSRGB,
    _In_ bool isCubeMap,
    _In_reads_opt_(mipCount* arraySize) D3D12_SUBRESOURCE_DATA* initData,
    ComPtr<ID3D12Resource>& texture,
    ComPtr<ID3D12Resource>& textureUploadHeap) {
  if (device == nullptr)
    return E_POINTER;

  if (forceSRGB)
    format = MakeSRGB(format);

  HRESULT hr = E_FAIL;
  switch (resDim) {
  case D3D12_RESOURCE_DIMENSION_TEXTURE2D: {
    D3D12_RESOURCE_DESC texDesc;
    ZeroMemory(&texDesc, sizeof(D3D12_RESOURCE_DESC));
    texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    texDesc.Alignment = 0;
    texDesc.Width = width;
    texDesc.Height = (uint32_t)height;
    texDesc.DepthOrArraySize =
        (depth > 1) ? (uint16_t)depth : (uint16_t)arraySize;
    texDesc.MipLevels = (uint16_t)mipCount;
    texDesc.Format = format;
    texDesc.SampleDesc.Count = 1;
    texDesc.SampleDesc.Quality = 0;
    texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
    texDesc.Flags = D3D12_RESOURCE_FLAG_NONE;

    CD3DX12_HEAP_PROPERTIES props(D3D12_HEAP_TYPE_DEFAULT);
    hr = device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &texDesc,
        D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&texture));

    if (FAILED(hr)) {
      texture = nullptr;
      return hr;
    } else {
      const UINT num2DSubresources =
          texDesc.DepthOrArraySize * texDesc.MipLevels;
      const UINT64 uploadBufferSize =
          GetRequiredIntermediateSize(texture.Get(), 0, num2DSubresources);

      CD3DX12_HEAP_PROPERTIES heap_props(D3D12_HEAP_TYPE_UPLOAD);
      auto buffer = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
      hr = device->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
          &buffer, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
          IID_PPV_ARGS(&textureUploadHeap));
      if (FAILED(hr)) {
        texture = nullptr;
        return hr;
      } else {
        auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(texture.Get(),
            D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
        cmdList->ResourceBarrier(1, &barrier);

        // Use Heap-allocating UpdateSubresources implementation for variable
        // number of subresources (which is the case for textures).
        UpdateSubresources(cmdList, texture.Get(), textureUploadHeap.Get(), 0,
            0, num2DSubresources, initData);

        auto barrier2 = CD3DX12_RESOURCE_BARRIER::Transition(texture.Get(),
            D3D12_RESOURCE_STATE_COPY_DEST,
            D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
        cmdList->ResourceBarrier(1, &barrier2);
      }
    }
  } break;
  }

  return hr;
}

//--------------------------------------------------------------------------------------
static HRESULT CreateTextureFromDDS(_In_ ID3D11Device* d3dDevice,
    _In_opt_ ID3D11DeviceContext* d3dContext, _In_ const DDS_HEADER* header,
    _In_reads_bytes_(bitSize) const uint8_t* bitData, _In_ size_t bitSize,
    _In_ size_t maxsize, _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags,
    _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags,
    _In_ bool forceSRGB, _Outptr_opt_ ID3D11Resource** texture,
    _Outptr_opt_ ID3D11ShaderResourceView** textureView) {
  HRESULT hr = S_OK;

  UINT width = header->width;
  UINT height = header->height;
  UINT depth = header->depth;

  uint32_t resDim = D3D11_RESOURCE_DIMENSION_UNKNOWN;
  UINT arraySize = 1;
  DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
  bool isCubeMap = false;

  size_t mipCount = header->mipMapCount;
  if (0 == mipCount) {
    mipCount = 1;
  }

  if ((header->ddspf.flags & DDS_FOURCC) &&
      (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) {
    auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>(
        (const char*)header + sizeof(DDS_HEADER));

    arraySize = d3d10ext->arraySize;
    if (arraySize == 0) {
      return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
    }

    switch (d3d10ext->dxgiFormat) {
    case DXGI_FORMAT_AI44:
    case DXGI_FORMAT_IA44:
    case DXGI_FORMAT_P8:
    case DXGI_FORMAT_A8P8:
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);

    default:
      if (BitsPerPixel(d3d10ext->dxgiFormat) == 0) {
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
      }
    }

    format = d3d10ext->dxgiFormat;

    switch (d3d10ext->resourceDimension) {
    case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
      // D3DX writes 1D textures with a fixed Height of 1
      if ((header->flags & DDS_HEIGHT) && height != 1) {
        return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
      }
      height = depth = 1;
      break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
      if (d3d10ext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE) {
        arraySize *= 6;
        isCubeMap = true;
      }
      depth = 1;
      break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
      if (!(header->flags & DDS_HEADER_FLAGS_VOLUME)) {
        return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
      }

      if (arraySize > 1) {
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
      }
      break;

    default:
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    resDim = d3d10ext->resourceDimension;
  } else {
    format = GetDXGIFormat(header->ddspf);

    if (format == DXGI_FORMAT_UNKNOWN) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    if (header->flags & DDS_HEADER_FLAGS_VOLUME) {
      resDim = D3D11_RESOURCE_DIMENSION_TEXTURE3D;
    } else {
      if (header->caps2 & DDS_CUBEMAP) {
        // We require all six faces to be defined
        if ((header->caps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES) {
          return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
        }

        arraySize = 6;
        isCubeMap = true;
      }

      depth = 1;
      resDim = D3D11_RESOURCE_DIMENSION_TEXTURE2D;

      // Note there's no way for a legacy Direct3D 9 DDS to express a '1D'
      // texture
    }

    assert(BitsPerPixel(format) != 0);
  }

  // Bound sizes (for security purposes we don't trust DDS file metadata larger
  // than the D3D 11.x hardware requirements)
  if (mipCount > D3D11_REQ_MIP_LEVELS) {
    return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
  }

  switch (resDim) {
  case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
    if ((arraySize > D3D11_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION) ||
        (width > D3D11_REQ_TEXTURE1D_U_DIMENSION)) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }
    break;

  case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
    if (isCubeMap) {
      // This is the right bound because we set arraySize to (NumCubes*6) above
      if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
          (width > D3D11_REQ_TEXTURECUBE_DIMENSION) ||
          (height > D3D11_REQ_TEXTURECUBE_DIMENSION)) {
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
      }
    } else if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
               (width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION) ||
               (height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION)) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }
    break;

  case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
    if ((arraySize > 1) || (width > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
        (height > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
        (depth > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }
    break;

  default:
    return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
  }

  bool autogen = false;
  if (mipCount == 1 && d3dContext != 0 &&
      textureView !=
          0) // Must have context and shader-view to auto generate mipmaps
  {
    // See if format is supported for auto-gen mipmaps (varies by feature level)
    UINT fmtSupport = 0;
    hr = d3dDevice->CheckFormatSupport(format, &fmtSupport);
    if (SUCCEEDED(hr) && (fmtSupport & D3D11_FORMAT_SUPPORT_MIP_AUTOGEN)) {
      // 10level9 feature levels do not support auto-gen mipgen for volume
      // textures
      if ((resDim != D3D11_RESOURCE_DIMENSION_TEXTURE3D) ||
          (d3dDevice->GetFeatureLevel() >= D3D_FEATURE_LEVEL_10_0)) {
        autogen = true;
      }
    }
  }

  if (autogen) {
    // Create texture with auto-generated mipmaps
    ID3D11Resource* tex = nullptr;
    hr = CreateD3DResources(d3dDevice, resDim, width, height, depth, 0,
        arraySize, format, usage, bindFlags | D3D11_BIND_RENDER_TARGET,
        cpuAccessFlags, miscFlags | D3D11_RESOURCE_MISC_GENERATE_MIPS,
        forceSRGB, isCubeMap, nullptr, &tex, textureView);
    if (SUCCEEDED(hr)) {
      size_t numBytes = 0;
      size_t rowBytes = 0;
      GetSurfaceInfo(width, height, format, &numBytes, &rowBytes, nullptr);

      if (numBytes > bitSize) {
        (*textureView)->Release();
        *textureView = nullptr;
        tex->Release();
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
      }

      D3D11_SHADER_RESOURCE_VIEW_DESC desc;
      (*textureView)->GetDesc(&desc);

      UINT mipLevels = 1;

      switch (desc.ViewDimension) {
      case D3D_SRV_DIMENSION_TEXTURE1D:
        mipLevels = desc.Texture1D.MipLevels;
        break;
      case D3D_SRV_DIMENSION_TEXTURE1DARRAY:
        mipLevels = desc.Texture1DArray.MipLevels;
        break;
      case D3D_SRV_DIMENSION_TEXTURE2D:
        mipLevels = desc.Texture2D.MipLevels;
        break;
      case D3D_SRV_DIMENSION_TEXTURE2DARRAY:
        mipLevels = desc.Texture2DArray.MipLevels;
        break;
      case D3D_SRV_DIMENSION_TEXTURECUBE:
        mipLevels = desc.TextureCube.MipLevels;
        break;
      case D3D_SRV_DIMENSION_TEXTURECUBEARRAY:
        mipLevels = desc.TextureCubeArray.MipLevels;
        break;
      case D3D_SRV_DIMENSION_TEXTURE3D:
        mipLevels = desc.Texture3D.MipLevels;
        break;
      default:
        (*textureView)->Release();
        *textureView = nullptr;
        tex->Release();
        return E_UNEXPECTED;
      }

      if (arraySize > 1) {
        const uint8_t* pSrcBits = bitData;
        const uint8_t* pEndBits = bitData + bitSize;
        for (UINT item = 0; item < arraySize; ++item) {
          if ((pSrcBits + numBytes) > pEndBits) {
            (*textureView)->Release();
            *textureView = nullptr;
            tex->Release();
            return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
          }

          UINT res = D3D11CalcSubresource(0, item, mipLevels);
          d3dContext->UpdateSubresource(tex, res, nullptr, pSrcBits,
              static_cast<UINT>(rowBytes), static_cast<UINT>(numBytes));
          pSrcBits += numBytes;
        }
      } else {
        d3dContext->UpdateSubresource(tex, 0, nullptr, bitData,
            static_cast<UINT>(rowBytes), static_cast<UINT>(numBytes));
      }

      d3dContext->GenerateMips(*textureView);

      if (texture) {
        *texture = tex;
      } else {
        tex->Release();
      }
    }
  } else {
    // Create the texture
    std::unique_ptr<D3D11_SUBRESOURCE_DATA[]> initData(
        new (std::nothrow) D3D11_SUBRESOURCE_DATA[mipCount * arraySize]);
    if (!initData) {
      return E_OUTOFMEMORY;
    }

    size_t skipMip = 0;
    size_t twidth = 0;
    size_t theight = 0;
    size_t tdepth = 0;
    hr =
        FillInitData(width, height, depth, mipCount, arraySize, format, maxsize,
            bitSize, bitData, twidth, theight, tdepth, skipMip, initData.get());

    if (SUCCEEDED(hr)) {
      hr = CreateD3DResources(d3dDevice, resDim, twidth, theight, tdepth,
          mipCount - skipMip, arraySize, format, usage, bindFlags,
          cpuAccessFlags, miscFlags, forceSRGB, isCubeMap, initData.get(),
          texture, textureView);

      if (FAILED(hr) && !maxsize && (mipCount > 1)) {
        // Retry with a maxsize determined by feature level
        switch (d3dDevice->GetFeatureLevel()) {
        case D3D_FEATURE_LEVEL_9_1:
        case D3D_FEATURE_LEVEL_9_2:
          if (isCubeMap) {
            maxsize = 512 /*D3D_FL9_1_REQ_TEXTURECUBE_DIMENSION*/;
          } else {
            maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
                          ? 256 /*D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION*/
                          : 2048 /*D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION*/;
          }
          break;

        case D3D_FEATURE_LEVEL_9_3:
          maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
                        ? 256 /*D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION*/
                        : 4096 /*D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION*/;
          break;

        default: // D3D_FEATURE_LEVEL_10_0 & D3D_FEATURE_LEVEL_10_1
          maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
                        ? 2048 /*D3D10_REQ_TEXTURE3D_U_V_OR_W_DIMENSION*/
                        : 8192 /*D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION*/;
          break;
        }

        hr = FillInitData(width, height, depth, mipCount, arraySize, format,
            maxsize, bitSize, bitData, twidth, theight, tdepth, skipMip,
            initData.get());
        if (SUCCEEDED(hr)) {
          hr = CreateD3DResources(d3dDevice, resDim, twidth, theight, tdepth,
              mipCount - skipMip, arraySize, format, usage, bindFlags,
              cpuAccessFlags, miscFlags, forceSRGB, isCubeMap, initData.get(),
              texture, textureView);
        }
      }
    }
  }

  return hr;
}

static HRESULT CreateTextureFromDDS12(_In_ ID3D12Device* device,
    _In_opt_ ID3D12GraphicsCommandList* cmdList, _In_ const DDS_HEADER* header,
    _In_reads_bytes_(bitSize) const uint8_t* bitData, _In_ size_t bitSize,
    _In_ size_t maxsize, _In_ bool forceSRGB, ComPtr<ID3D12Resource>& texture,
    ComPtr<ID3D12Resource>& textureUploadHeap) {
  HRESULT hr = S_OK;

  UINT width = header->width;
  UINT height = header->height;
  UINT depth = header->depth;

  uint32_t resDim = D3D12_RESOURCE_DIMENSION_UNKNOWN;
  UINT arraySize = 1;
  DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
  bool isCubeMap = false;

  size_t mipCount = header->mipMapCount;
  if (0 == mipCount)
    mipCount = 1;

  if ((header->ddspf.flags & DDS_FOURCC) &&
      (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) {
    auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>(
        (const char*)header + sizeof(DDS_HEADER));

    arraySize = d3d10ext->arraySize;
    if (arraySize == 0)
      return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);

    switch (d3d10ext->dxgiFormat) {
    case DXGI_FORMAT_AI44:
    case DXGI_FORMAT_IA44:
    case DXGI_FORMAT_P8:
    case DXGI_FORMAT_A8P8:
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);

    default:
      if (BitsPerPixel(d3d10ext->dxgiFormat) == 0)
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    format = d3d10ext->dxgiFormat;

    switch (d3d10ext->resourceDimension) {
    case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
      if ((header->flags & DDS_HEIGHT) && height != 1)
        return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
      height = depth = 1;
      break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
      if (d3d10ext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE) {
        arraySize *= 6;
        isCubeMap = true;
      }
      depth = 1;
      break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
      if (!(header->flags & DDS_HEADER_FLAGS_VOLUME))
        return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
      if (arraySize > 1)
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
      break;

    default:
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    switch (d3d10ext->resourceDimension) {
    case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
      resDim = D3D12_RESOURCE_DIMENSION_TEXTURE1D;
      break;
    case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
      resDim = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
      break;
    case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
      resDim = D3D12_RESOURCE_DIMENSION_TEXTURE3D;
      break;
    }
  } else {
    format = GetDXGIFormat(header->ddspf);

    if (format == DXGI_FORMAT_UNKNOWN)
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);

    if (header->flags & DDS_HEADER_FLAGS_VOLUME) {
      resDim = D3D12_RESOURCE_DIMENSION_TEXTURE3D;
    } else {
      if (header->caps2 & DDS_CUBEMAP) {
        if ((header->caps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES)
          return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
        arraySize = 6;
        isCubeMap = true;
      }

      depth = 1;
      resDim = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    }

    assert(BitsPerPixel(format) != 0);
  }

  // Bound sizes (for security purposes we don't trust DDS file metadata larger
  // than the D3D 11.x hardware requirements)
  if (mipCount > D3D12_REQ_MIP_LEVELS) {
    return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
  }

  switch (resDim) {
  case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
    if ((arraySize > D3D12_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION) ||
        (width > D3D12_REQ_TEXTURE1D_U_DIMENSION)) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }
    break;

  case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
    if (isCubeMap) {
      // This is the right bound because we set arraySize to (NumCubes*6) above
      if ((arraySize > D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
          (width > D3D12_REQ_TEXTURECUBE_DIMENSION) ||
          (height > D3D12_REQ_TEXTURECUBE_DIMENSION)) {
        return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
      }
    } else if ((arraySize > D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
               (width > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION) ||
               (height > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION)) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }
    break;

  case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
    if ((arraySize > 1) || (width > D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
        (height > D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
        (depth > D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)) {
      return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }
    break;

  default:
    return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
  }

  // Create the texture
  std::unique_ptr<D3D12_SUBRESOURCE_DATA[]> initData(
      new (std::nothrow) D3D12_SUBRESOURCE_DATA[mipCount * arraySize]);

  if (!initData) {
    return E_OUTOFMEMORY;
  }

  size_t skipMip = 0;
  size_t twidth = 0;
  size_t theight = 0;
  size_t tdepth = 0;

  hr =
      FillInitData12(width, height, depth, mipCount, arraySize, format, maxsize,
          bitSize, bitData, twidth, theight, tdepth, skipMip, initData.get());

  if (SUCCEEDED(hr)) {
    hr = CreateD3DResources12(device, cmdList, resDim, twidth, theight, tdepth,
        mipCount - skipMip, arraySize, format,
        false, // forceSRGB
        isCubeMap, initData.get(), texture, textureUploadHeap);
  }

  return hr;
}

//--------------------------------------------------------------------------------------
static DDS_ALPHA_MODE GetAlphaMode(_In_ const DDS_HEADER* header) {
  if (header->ddspf.flags & DDS_FOURCC) {
    if (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC) {
      auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>(
          (const char*)header + sizeof(DDS_HEADER));
      auto mode = static_cast<DDS_ALPHA_MODE>(
          d3d10ext->miscFlags2 & DDS_MISC_FLAGS2_ALPHA_MODE_MASK);
      switch (mode) {
      case DDS_ALPHA_MODE_STRAIGHT:
      case DDS_ALPHA_MODE_PREMULTIPLIED:
      case DDS_ALPHA_MODE_OPAQUE:
      case DDS_ALPHA_MODE_CUSTOM:
        return mode;
      }
    } else if ((MAKEFOURCC('D', 'X', 'T', '2') == header->ddspf.fourCC) ||
               (MAKEFOURCC('D', 'X', 'T', '4') == header->ddspf.fourCC)) {
      return DDS_ALPHA_MODE_PREMULTIPLIED;
    }
  }

  return DDS_ALPHA_MODE_UNKNOWN;
}

//--------------------------------------------------------------------------------------
_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromMemory(
    ID3D11Device* d3dDevice, const uint8_t* ddsData, size_t ddsDataSize,
    ID3D11Resource** texture, ID3D11ShaderResourceView** textureView,
    size_t maxsize, DDS_ALPHA_MODE* alphaMode) {
  return CreateDDSTextureFromMemoryEx(d3dDevice, nullptr, ddsData, ddsDataSize,
      maxsize, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
      texture, textureView, alphaMode);
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromMemory12(
    ID3D12Device* device, _In_ ID3D12GraphicsCommandList* cmdList,
    _In_reads_bytes_(ddsDataSize) const uint8_t* ddsData,
    _In_ size_t ddsDataSize, ComPtr<ID3D12Resource>& texture,
    ComPtr<ID3D12Resource>& textureUploadHeap, _In_ size_t maxsize,
    _Out_opt_ DDS_ALPHA_MODE* alphaMode) {
  if (alphaMode)
    (*alphaMode) = DDS_ALPHA_MODE_UNKNOWN;

  if (!device || !cmdList || !ddsData || !ddsDataSize) {
    return E_INVALIDARG;
  }

  uint32_t dwMagicNumber = *(const uint32_t*)(ddsData);
  if (dwMagicNumber != DDS_MAGIC) {
    return E_FAIL;
  }

  auto header = reinterpret_cast<const DDS_HEADER*>(ddsData + sizeof(uint32_t));

  // Verify header to validate DDS file
  if (header->size != sizeof(DDS_HEADER) ||
      header->ddspf.size != sizeof(DDS_PIXELFORMAT)) {
    return E_FAIL;
  }

  // Check for DX10 extension
  bool bDXT10Header = false;
  if ((header->ddspf.flags & DDS_FOURCC) &&
      (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) {
    // Must be long enough for both headers and magic value
    if (ddsDataSize <
        (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_DXT10))) {
      return E_FAIL;
    }

    bDXT10Header = true;
  }

  ptrdiff_t offset = sizeof(uint32_t) + sizeof(DDS_HEADER) +
                     (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);

  HRESULT hr = CreateTextureFromDDS12(device, cmdList, header, ddsData + offset,
      ddsDataSize - offset, maxsize, false, texture, textureUploadHeap);

  if (SUCCEEDED(hr)) {
    if (alphaMode)
      (*alphaMode) = GetAlphaMode(header);
  }

  return hr;
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromMemory(
    ID3D11Device* d3dDevice, ID3D11DeviceContext* d3dContext,
    const uint8_t* ddsData, size_t ddsDataSize, ID3D11Resource** texture,
    ID3D11ShaderResourceView** textureView, size_t maxsize,
    DDS_ALPHA_MODE* alphaMode) {
  return CreateDDSTextureFromMemoryEx(d3dDevice, d3dContext, ddsData,
      ddsDataSize, maxsize, D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0,
      0, false, texture, textureView, alphaMode);
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromMemoryEx(
    ID3D11Device* d3dDevice, const uint8_t* ddsData, size_t ddsDataSize,
    size_t maxsize, D3D11_USAGE usage, unsigned int bindFlags,
    unsigned int cpuAccessFlags, unsigned int miscFlags, bool forceSRGB,
    ID3D11Resource** texture, ID3D11ShaderResourceView** textureView,
    DDS_ALPHA_MODE* alphaMode) {
  return CreateDDSTextureFromMemoryEx(d3dDevice, nullptr, ddsData, ddsDataSize,
      maxsize, usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB, texture,
      textureView, alphaMode);
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromMemoryEx(
    ID3D11Device* d3dDevice, ID3D11DeviceContext* d3dContext,
    const uint8_t* ddsData, size_t ddsDataSize, size_t maxsize,
    D3D11_USAGE usage, unsigned int bindFlags, unsigned int cpuAccessFlags,
    unsigned int miscFlags, bool forceSRGB, ID3D11Resource** texture,
    ID3D11ShaderResourceView** textureView, DDS_ALPHA_MODE* alphaMode) {
  if (texture) {
    *texture = nullptr;
  }
  if (textureView) {
    *textureView = nullptr;
  }
  if (alphaMode) {
    *alphaMode = DDS_ALPHA_MODE_UNKNOWN;
  }

  if (!d3dDevice || !ddsData || (!texture && !textureView)) {
    return E_INVALIDARG;
  }

  // Validate DDS file in memory
  if (ddsDataSize < (sizeof(uint32_t) + sizeof(DDS_HEADER))) {
    return E_FAIL;
  }

  uint32_t dwMagicNumber = *(const uint32_t*)(ddsData);
  if (dwMagicNumber != DDS_MAGIC) {
    return E_FAIL;
  }

  auto header = reinterpret_cast<const DDS_HEADER*>(ddsData + sizeof(uint32_t));

  // Verify header to validate DDS file
  if (header->size != sizeof(DDS_HEADER) ||
      header->ddspf.size != sizeof(DDS_PIXELFORMAT)) {
    return E_FAIL;
  }

  // Check for DX10 extension
  bool bDXT10Header = false;
  if ((header->ddspf.flags & DDS_FOURCC) &&
      (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) {
    // Must be long enough for both headers and magic value
    if (ddsDataSize <
        (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_DXT10))) {
      return E_FAIL;
    }

    bDXT10Header = true;
  }

  ptrdiff_t offset = sizeof(uint32_t) + sizeof(DDS_HEADER) +
                     (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);

  HRESULT hr = CreateTextureFromDDS(d3dDevice, d3dContext, header,
      ddsData + offset, ddsDataSize - offset, maxsize, usage, bindFlags,
      cpuAccessFlags, miscFlags, forceSRGB, texture, textureView);
  if (SUCCEEDED(hr)) {
    if (texture != 0 && *texture != 0) {
      SetDebugObjectName(*texture, "DDSTextureLoader");
    }

    if (textureView != 0 && *textureView != 0) {
      SetDebugObjectName(*textureView, "DDSTextureLoader");
    }

    if (alphaMode)
      *alphaMode = GetAlphaMode(header);
  }

  return hr;
}

//--------------------------------------------------------------------------------------
_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromFile(
    ID3D11Device* d3dDevice, const wchar_t* fileName, ID3D11Resource** texture,
    ID3D11ShaderResourceView** textureView, size_t maxsize,
    DDS_ALPHA_MODE* alphaMode) {
  return CreateDDSTextureFromFileEx(d3dDevice, nullptr, fileName, maxsize,
      D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false, texture,
      textureView, alphaMode);
}

HRESULT DirectX::CreateDDSTextureFromFile12(_In_ ID3D12Device* device,
    _In_ ID3D12GraphicsCommandList* cmdList, _In_z_ const wchar_t* szFileName,
    _Out_ ComPtr<ID3D12Resource>& texture,
    _Out_ ComPtr<ID3D12Resource>& textureUploadHeap, _In_ size_t maxsize,
    _Out_opt_ DDS_ALPHA_MODE* alphaMode) {
  if (texture) {
    texture = nullptr;
  }
  if (textureUploadHeap) {
    textureUploadHeap = nullptr;
  }
  if (alphaMode) {
    *alphaMode = DDS_ALPHA_MODE_UNKNOWN;
  }

  if (!device || !szFileName) {
    return E_INVALIDARG;
  }

  DDS_HEADER* header = nullptr;
  uint8_t* bitData = nullptr;
  size_t bitSize = 0;

  std::unique_ptr<uint8_t[]> ddsData;
  HRESULT hr =
      LoadTextureDataFromFile(szFileName, ddsData, &header, &bitData, &bitSize);
  if (FAILED(hr)) {
    return hr;
  }

  hr = CreateTextureFromDDS12(device, cmdList, header, bitData, bitSize,
      maxsize, false, texture, textureUploadHeap);

  if (SUCCEEDED(hr)) {
    /*
    #if !defined(NO_D3D11_DEBUG_NAME) && ( defined(_DEBUG) || defined(PROFILE) )
                    if (texture != 0 || textureView != 0)
                    {
                            CHAR strFileA[MAX_PATH];
                            int result = WideCharToMultiByte(CP_ACP,
                                    WC_NO_BEST_FIT_CHARS,
                                    fileName,
                                    -1,
                                    strFileA,
                                    MAX_PATH,
                                    nullptr,
                                    FALSE
                                    );
                            if (result > 0)
                            {
                                    const CHAR* pstrName = strrchr(strFileA,
    '\\'); if (!pstrName)
                                    {
                                            pstrName = strFileA;
                                    }
                                    else
                                    {
                                            pstrName++;
                                    }

                                    if (texture != 0 && *texture != 0)
                                    {
                                            (*texture)->SetPrivateData(WKPDID_D3DDebugObjectName,
                                                    static_cast<UINT>(strnlen_s(pstrName,
    MAX_PATH)), pstrName
                                                    );
                                    }

                                    if (textureView != 0 && *textureView != 0)
                                    {
                                            (*textureView)->SetPrivateData(WKPDID_D3DDebugObjectName,
                                                    static_cast<UINT>(strnlen_s(pstrName,
    MAX_PATH)), pstrName
                                                    );
                                    }
                            }
                    }
    #endif
    */
    if (alphaMode)
      *alphaMode = GetAlphaMode(header);
  }

  return hr;
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromFile(
    ID3D11Device* d3dDevice, ID3D11DeviceContext* d3dContext,
    const wchar_t* fileName, ID3D11Resource** texture,
    ID3D11ShaderResourceView** textureView, size_t maxsize,
    DDS_ALPHA_MODE* alphaMode) {
  return CreateDDSTextureFromFileEx(d3dDevice, d3dContext, fileName, maxsize,
      D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false, texture,
      textureView, alphaMode);
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromFileEx(
    ID3D11Device* d3dDevice, const wchar_t* fileName, size_t maxsize,
    D3D11_USAGE usage, unsigned int bindFlags, unsigned int cpuAccessFlags,
    unsigned int miscFlags, bool forceSRGB, ID3D11Resource** texture,
    ID3D11ShaderResourceView** textureView, DDS_ALPHA_MODE* alphaMode) {
  return CreateDDSTextureFromFileEx(d3dDevice, nullptr, fileName, maxsize,
      usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB, texture,
      textureView, alphaMode);
}

_Use_decl_annotations_ HRESULT DirectX::CreateDDSTextureFromFileEx(
    ID3D11Device* d3dDevice, ID3D11DeviceContext* d3dContext,
    const wchar_t* fileName, size_t maxsize, D3D11_USAGE usage,
    unsigned int bindFlags, unsigned int cpuAccessFlags, unsigned int miscFlags,
    bool forceSRGB, ID3D11Resource** texture,
    ID3D11ShaderResourceView** textureView, DDS_ALPHA_MODE* alphaMode) {
  if (texture) {
    *texture = nullptr;
  }
  if (textureView) {
    *textureView = nullptr;
  }
  if (alphaMode) {
    *alphaMode = DDS_ALPHA_MODE_UNKNOWN;
  }

  if (!d3dDevice || !fileName || (!texture && !textureView)) {
    return E_INVALIDARG;
  }

  DDS_HEADER* header = nullptr;
  uint8_t* bitData = nullptr;
  size_t bitSize = 0;

  std::unique_ptr<uint8_t[]> ddsData;
  HRESULT hr =
      LoadTextureDataFromFile(fileName, ddsData, &header, &bitData, &bitSize);
  if (FAILED(hr)) {
    return hr;
  }

  hr = CreateTextureFromDDS(d3dDevice, d3dContext, header, bitData, bitSize,
      maxsize, usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB, texture,
      textureView);

  if (SUCCEEDED(hr)) {
#if !defined(NO_D3D11_DEBUG_NAME) && (defined(_DEBUG) || defined(PROFILE))
    if (texture != 0 || textureView != 0) {
      CHAR strFileA[MAX_PATH];
      int result = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, fileName,
          -1, strFileA, MAX_PATH, nullptr, FALSE);
      if (result > 0) {
        const CHAR* pstrName = strrchr(strFileA, '\\');
        if (!pstrName) {
          pstrName = strFileA;
        } else {
          pstrName++;
        }

        if (texture != 0 && *texture != 0) {
          (*texture)->SetPrivateData(WKPDID_D3DDebugObjectName,
              static_cast<UINT>(strnlen_s(pstrName, MAX_PATH)), pstrName);
        }

        if (textureView != 0 && *textureView != 0) {
          (*textureView)
              ->SetPrivateData(WKPDID_D3DDebugObjectName,
                  static_cast<UINT>(strnlen_s(pstrName, MAX_PATH)), pstrName);
        }
      }
    }
#endif

    if (alphaMode)
      *alphaMode = GetAlphaMode(header);
  }

  return hr;
}
